package main

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"reflect"
	"time"
)

var PORT = ":8080"

type handler struct {
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	clientCert := req.TLS.PeerCertificates[0]
	subject := clientCert.Subject
	number := clientCert.SerialNumber

	fmt.Println(subject.CommonName)
	fmt.Println(number)
	w.Write([]byte("Hello world!\n"))
}

func main() {
	//caCert, err := ioutil.ReadFile("./ca/ca.cert.pem")
	//if err != nil {
	//	fmt.Println(err)
	//	return
	//}
	mcaCert, err := ioutil.ReadFile("./mca/mca.cert.pem")
	if err != nil {
		fmt.Println(err)
		return
	}

	caCertPool := x509.NewCertPool()
	//caCertPool.AppendCertsFromPEM(caCert)
	caCertPool.AppendCertsFromPEM(mcaCert)

	// 服务端的配置
	cfg := &tls.Config{
		// 回调将在完成正常的证书链验证后调用（例如确保证书属于受信任的 CA 并使用允许的密码）
		// 如果此回调返回nil，将继续进行 TLS 握手，否则将中止。
		VerifyPeerCertificate: VerifyPeerCertificate,
		// 对客户端证书的校验规则，默认NoClientCert，不需要客户端提供证书
		// RequireAndVerifyClientCert表示需要客户端提供证书并对其校验
		ClientAuth: tls.RequireAndVerifyClientCert,
		// 用于校验客户端证书的ca证书，可以只加载mca
		ClientCAs: caCertPool,
	}

	srv := &http.Server{
		Addr:      PORT,
		Handler:   &handler{},
		TLSConfig: cfg,
	}
	//http.HandleFunc("/hello", ServeHTTP)
	fmt.Println("listening to port number", PORT)
	log.Fatal(srv.ListenAndServeTLS("./server/cadmus.cert.pem", "./server/cadmus.key.pem"))

	// 开启http
	//srv2 := &http.Server{
	//	Addr: PORT,
	//	Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	//		io.WriteString(w, "Hello, TLS!\n")
	//	}),
	//}
	//
	//fmt.Println("listening to port number", PORT)
	//go func() {
	//	if err := srv2.ListenAndServe(); err != nil && err != http.ErrServerClosed {
	//		log.Fatalf(err.Error(), "Listen: %s\n")
	//	}
	//}()

}

// TODO 严格的校验规则，应该是先检查本地CRL，然后检查CRL是否有更新以及OCSP
// TODO 业务上服务端对客户端的检测是主动的，客户端检测服务端证书则可以使用ocsp stapling优化

// VerifyPeerCertificate 在回调中实现CRL校验
func VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	// 校验是否需要证书

	// 校验证书是否失效
	derBytes, err := ioutil.ReadFile("./mca/crl.pem")
	if err != nil {
		fmt.Println(err)
		return err
	}
	certList, err := x509.ParseCRL(derBytes)
	if err != nil {
		fmt.Println(err)
		return err
	}
	// CRL是否过期
	if certList.HasExpired(time.Now()) {
		fmt.Errorf("CRL has expired (but shouldn't have)")
	}

	//var certificates []pkix.RevokedCertificate
	certificates := certList.TBSCertList.RevokedCertificates
	for i := range certificates {
		if reflect.DeepEqual(certificates[i].SerialNumber, verifiedChains[0][0].SerialNumber) {
			fmt.Errorf("证书 %s 被吊销", certificates[i].SerialNumber)
			return errors.New("证书被吊销")
		}
	}
	fmt.Println(certificates)
	return nil
}

// VerifyPeerCertificate 在回调中实现OCSP校验
//func VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
//	// 创建ocsp请求
//	opts := &ocsp.RequestOptions{Hash: crypto.SHA256}
//	buffer, err := ocsp.CreateRequest(verifiedChains[0][0], verifiedChains[0][1], opts)
//	if err != nil {
//		return err
//	}
//
//	// 发送ocsp请求数据到ocsp服务器
//	httpRequest, err := http.NewRequest(http.MethodPost, verifiedChains[0][0].OCSPServer[0], bytes.NewBuffer(buffer))
//	if err != nil {
//		return err
//	}
//	ocspURL, err := url.Parse(verifiedChains[0][0].OCSPServer[0])
//	if err != nil {
//		return err
//	}
//	httpRequest.Header.Add("Content-Type", "application/ocsp-request")
//	httpRequest.Header.Add("Accept", "application/ocsp-response")
//	httpRequest.Header.Add("host", ocspURL.Host)
//
//	httpClient := &http.Client{}
//	httpResponse, err := httpClient.Do(httpRequest)
//	if err != nil {
//		return err
//	}
//	defer httpResponse.Body.Close()
//	output, err := ioutil.ReadAll(httpResponse.Body)
//	if err != nil {
//		return err
//	}
//	// 响应结果解析
//	ocspResponse, err := ocsp.ParseResponseForCert(output, verifiedChains[0][0], verifiedChains[0][1])
//	if err != nil {
//		return err
//	}
//	switch ocspResponse.Status {
//	case ocsp.Good:
//		fmt.Println(ocsp.Good)
//		return nil
//	case ocsp.Revoked:
//		//at := ocspResponse.RevokedAt
//		//reason := ocspResponse.RevocationReason
//		fmt.Println(ocsp.Revoked)
//		return nil
//	case ocsp.Unknown:
//		fmt.Println(ocsp.Unknown)
//		return nil
//	case ocsp.ServerFailed:
//		fmt.Println(ocsp.ServerFailed)
//		return nil
//	}
//
//	return nil
//}
